/** @file    MediaDirectorBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: MediaDirectorBundle.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the wosh::services::MediaDirectorBundle class.
 * The header for this class can be found in MediaDirectorBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "MediaDirectorBundle.h"

 #include "MediaDirectorImpl.h"
 #include "MediaDirectorParser.h"

 #include <core/Fact.h>
 #include <core/List.h>
 #include <core/Table.h>

 #include <core/MessageProcessingCommon.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>
 #include <core/PersistenceManager.h>
 #include <framework/communication/AudioMessage.h>
 #include <interfaces/devices/PlayerAudio.h>

using namespace std;
using namespace wosh::communication;
using namespace wosh::database;
using namespace wosh::interfaces::devices;
namespace wosh {
 namespace services {

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

MediaDirectorBundle::MediaDirectorBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _MediaDirector_NAME, false );
	Object::getURI().setKernelLocal();
	Object::getURI().clearPathName();
	Object::getURI().setName( "Media" );
	Object::getURI().registerGlobal();
	Properties.update( _KEY_Version, _MediaDirector_VERSION );

	this->PlaylistVirtualURI.setParent( Object::getURI() );
	this->PlaylistVirtualURI.setName("Playlist");
	this->PlaylistVirtualURI.registerGlobal();
	this->ZoneVirtualURI.setParent( Object::getURI() );
	this->ZoneVirtualURI.setName("Zones");
	this->ZoneVirtualURI.registerGlobal();


	Log(LOG_DEBUG, " Configuring MediaDirectorImpl worker.." );
	this->mediaWorker = new MediaDirectorImpl(*this, this->BusDevices);
	this->mediaWorker->setListener(this);

	this->zoneParser = new MediaDirectorParser(*this, this->mediaWorker->getZones(), this->BusDevices, this->BusBuilding);
	this->zoneParser->setThreadListener(NULL); /// to be fixed
	this->zoneParser->setListener(this);

Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _CommunicatorService_KEY_ChannelType,	(int)Notification::TYPE_AUDIO, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_ChannelLink,	(int)Communicator::LINK_ASYNCH, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Cost,			0.0,Permission(Permission::RW, Permission::Read, Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Priority,		10,	Permission(Permission::RW, Permission::RW, Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Reliability,	10,	Permission(Permission::RW, Permission::RW, Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Protocol,		(int)Notification::PROTOCOL_HUMAN,	Permission(Permission::RW, Permission::Read, Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Locations,		"home",	Permission(Permission::RW, Permission::Read, Permission::Read) );

	Properties.set( _MediaDirector_KEY_count,				0,	Permission(Permission::Read) );
	Properties.set( _MediaDirector_KEY_MediaZoneDB,			"$DATABASE/entertainment/mediazones.xml", Permission(Permission::RW, Permission::RW, Permission::Read) );
	Properties.set( _MediaDirector_KEY_MediaZoneDB_shadow,	"$DATABASE/entertainment/mediazones.shadow", Permission(Permission::Read) );
	Properties.set( _MediaDirector_KEY_MediaListDB,			"$DATABASE/entertainment/medialists", Permission(Permission::Read) );
	Properties.set( _MediaDirector_KEY_MediaListDB_shadow,	"$DATABASE/entertainment/medialists/all.shadow", Permission(Permission::Read) );

	Properties.set( _MediaDirector_KEY_MediaTrackDB,		"MediaDirector_MediaTrackDB", Permission(Permission::RW, Permission::RW, Permission::Read) );

	Log(LOG_DEBUG, " Registering methods.." );
	MethodRequest* mmListZones = Methods.create<MethodRequest>( _METHOD_List, "List Media Zones" );
	mmListZones->setMethod( this, (MethodRequestPtr)&MediaDirectorBundle::mmDoListZones );
	mmListZones->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodList* mmListZones = Methods.create<MethodList>( _METHOD_List, "List Media Zones", Permission(Permission::RX) );
	mmListZones->setDataFieldExtractor( DataFieldExtractor<MediaZone>::createFor< DataAdapterMapT<string, MediaZone> >(this->mediaWorker->getZones()) );

	MethodRequest* mmListPlayList = Methods.create<MethodRequest>( _MediaDirector_METHOD_list_playlists, "List Media Lists" );
	mmListPlayList->setMethod( this, (MethodRequestPtr)&MediaDirectorBundle::mmDoListPlayList );
	mmListPlayList->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );
	MethodRequest* mmGetMatrix = Methods.create<MethodRequest>( _MediaDirector_METHOD_get_matrix, "Get Media Matrix" );
	mmGetMatrix->setMethod( this, (MethodRequestPtr)&MediaDirectorBundle::mmDoGetMatrix );
	mmGetMatrix->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodMessageResponse* mmListJoin = Methods.create<MethodMessageResponse>( _MediaDirector_METHOD_list_join, "Join MediaList to location..TBD" );
	mmListJoin->setMethod( this, (MethodMessageResponsePtr)&MediaDirectorBundle::mmDoListJoin );
	mmListJoin->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodMessageResponse* mmPlay = Methods.create<MethodMessageResponse>( _MediaDirector_METHOD_play, "play MediaList to location" );
	mmPlay->setMethod( this, (MethodMessageResponsePtr)&MediaDirectorBundle::mmDoPlay );
	mmPlay->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodRequest* mmGetPlayList = Methods.create<MethodRequest>( _MediaDirector_METHOD_get_playlist, "Get MediaList" );
	mmGetPlayList->setMethod( this, (MethodRequestPtr)&MediaDirectorBundle::mmDoGetPlayList );
	mmGetPlayList->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodWRESULT* mmUpdatePlayers = Methods.create<MethodWRESULT>( _MediaDirector_METHOD_forceupdate, "force update Players" );
	mmUpdatePlayers->setMethod( this, (MethodWRESULTPtr)&MediaDirectorBundle::updateZones );
	mmUpdatePlayers->getPermission().setMask( Permission::RX, Permission::Read, Permission::Read );

	MethodMessageResponse* mmSendMessageToUser = Methods.create<MethodMessageResponse>( _CommunicatorService_METHOD_sendmessage, "send message to (wosh) username" );
	mmSendMessageToUser->setMethod( this, (MethodMessageResponsePtr)&MediaDirectorBundle::mmDoSendMessageToUser );
	mmSendMessageToUser->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	setBundleState(Bundle::STATE_CREATED, false);
 }

MediaDirectorBundle::~MediaDirectorBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~MediaDirectorBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	delete this->mediaWorker; this->mediaWorker = NULL;
	delete this->zoneParser; this->zoneParser = NULL;
	Log(LOG_VERBOSE, ":~MediaDirectorBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT MediaDirectorBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	BusDevices.setMessageHandler(this);
	WRESULT bus_d_connected = BusDevices.connect( _Bus_Devices );
	if ( WFAILED(bus_d_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Devices")..", bus_d_connected );

	BusBuilding.setMessageHandler(this);
	WRESULT bus_b_connected = BusBuilding.connect( _Bus_Building, true ); // create bus if missing
	if ( WFAILED(bus_b_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus (wosh.Bus.Building)..", bus_b_connected );

	WRESULT ret = WRET_OK;
	ret += this->zoneParser->startThread( 15000 );
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED waiting RUNNING state of zoneParser.." );
		return WRET_ERR_INTERNAL;
	 }
	ret += BundleGeneric::start_SynchThread( this->mediaWorker );
	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event()

	WRESULT ret_loaded = loadMediaZones();
	if ( WFAILED(ret_loaded) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d loading MediaZone(s)", ret_loaded );

	ret_loaded = loadMediaLists();
	if ( WFAILED(ret_loaded) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d loading MediaList(s)", ret_loaded );

	Response* response = new Response( _METHOD_ListProperties, Properties.toTable(false) );
	Message* message = new Message(response);
	message->setSource(this);
	message->getDestination().fromString("*:Communication");
	message->setSecurityToken( generateCredentials() );
	BusCore.postMessage(message);

	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT MediaDirectorBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT bus_d_disconnected = BusDevices.disconnect();
	if ( WFAILED(bus_d_disconnected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStop", "FAILED#%d Disconnecting Bus ("_Bus_Devices")..", bus_d_disconnected );

	WRESULT bus_b_disconnected = BusBuilding.disconnect();
	if ( WFAILED(bus_b_disconnected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStop", "FAILED#%d Disconnecting Bus (wosh.Bus.Building)..", bus_b_disconnected );

	WRESULT ret = WRET_OK;
	ret += this->zoneParser->quitThread( 15000 );
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStop", "FAILED waiting STOPPED state of zoneParser.." );
		return WRET_ERR_INTERNAL;
	 }
	ret += BundleGeneric::stop_SynchThread( this->mediaWorker );
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event()

	WRESULT ret_saved = saveMediaZones();
	if ( WFAILED(ret_saved) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStop", "FAILED#%d saving MediaZone(s)", ret_saved );

	ret_saved = saveMediaLists();
	if ( WFAILED(ret_saved) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d saving MediaList(s)", ret_saved );

	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void MediaDirectorBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	// avoid message replicas
	if ( !MessageFilter::filterReplica( message, &BusCore ) )
		return;

	if ( MessageFilter::isMessageTarget(message, this) ) {
		if ( isBundleRunning() ) {
			this->zoneParser->evalMessageIncoming(message);
			this->mediaWorker->evalMessageIncoming(message);

			if ( message.getContent()->isNotification() ) {
				if ( message.getDestination().isBroadcast() )
					return;
				const Notification* notif = message.getContent()->asNotification();
				WRESULT sent = notify( notif );
				Message* reply = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_sent, new Variant(notif->getID()), sent ) );
				BusCore.postMessage(reply);
				return;
			 }
		 }
		BundleGeneric::busMessage(message, source);
		return;
	 }

	if ( !message.getContent()->isRequest() ) return;
	if ( !message.getDestination().isChildOf(this->getURI(), false) )
		return;	

	string target_name = message.getDestination().getName();
	const Request* request = message.getContent()->asRequest();
	Message* reply = NULL;

	if ( target_name == "Zones" ) {
		if ( request->getMethod() == _METHOD_List ) {
			Method* method = Methods.find(_METHOD_List);
			if ( method == NULL ) {
				Log(LOG_WARNING, ":busMessage() FAILED : Method '%s' not found", request->getMethod().c_str() );
				reply = message.replyTo( new Response( request->getMethod(), new Variant("Method not found!"), WRET_ERR_PARAM ) );
			 }
			else
				reply = method->exec( &message );
		 }
		else {
			Log(LOG_WARNING, ":busMessage() FAILED : Method '%s' not found", request->getMethod().c_str() );
			reply = message.replyTo( new Response( request->getMethod(), new Variant("Method not found!"), WRET_ERR_PARAM ) );
		 }
	 }
	else if ( target_name == "Playlist" ) {
		if ( request->getMethod() == _METHOD_List ) {
			Method* method = Methods.find(_MediaDirector_METHOD_list_playlists);
			if ( method == NULL ) {
				Log(LOG_WARNING, ":busMessage() FAILED : Method '%s' not found", request->getMethod().c_str() );
				reply = message.replyTo( new Response( request->getMethod(), new Variant("Method not found!"), WRET_ERR_PARAM ) );
			 }
			else
				reply = method->exec( &message );
		 }
		else {
			Log(LOG_WARNING, ":busMessage() FAILED : Method '%s' not found", request->getMethod().c_str() );
			reply = message.replyTo( new Response( request->getMethod(), new Variant("Method not found!"), WRET_ERR_PARAM ) );
		 }
	 }

	if ( reply != NULL ) {
		reply->setSource( this );
		BusCore.postMessage(reply);
		return;
	 }


	if ( message.getDestination().isChildOf(this->PlaylistVirtualURI, true) ) {
		this->mediaWorker->getPlaylists().mutex().lockForWrite();
		MediaList* mList = this->mediaWorker->getPlaylists().key_( message.getDestination().getName() );
		busMessageForMediaList_( mList, message, source );
		this->mediaWorker->getPlaylists().mutex().unLock();
		return;
	 }
	else if ( message.getDestination().isChildOf(this->ZoneVirtualURI, true) ) {
		this->mediaWorker->getZones().mutex().lockForWrite();
		MediaZone* zone = this->mediaWorker->getZones().key_( message.getDestination().getName() );
		busMessageForZone_( zone, message, source );
		this->mediaWorker->getZones().mutex().unLock();
		return;
	 }




 }

void MediaDirectorBundle::busMessageForMediaList_( MediaList* mList, const Message& message, const Bus* )
 {
	if ( mList == NULL ) return;

	// check permission
	const Permission* jobPerm = &((const NetworkChannel*)mList)->getPermission();

	bool allowRead = SecurityManager::evalAuthorization( message.getSecurityToken(), jobPerm, Permission::Read );
	if ( !allowRead ) {
		Log(LOG_WARNING, ":busMessageForMediaList_() : Not allowed to read Host '%s' ", mList->getName().c_str() );
		return;
	 }

	const Request* request = message.getContent()->asRequest();
	Response* response = NULL;

	if ( request->getMethod() == _METHOD_GetProperty )
		response = MPC_mmDoPropertyGet( &message, mList->getProperties(), Log );

	else if ( request->getMethod() == _METHOD_SetProperty )
		response = MPC_mmDoPropertySet( &message, mList->getProperties(), Log );

	else if ( request->getMethod() == _METHOD_ListProperties )
		response = MPC_mmDoPropertyList( &message, mList->getProperties(), Log );

	else if ( request->getMethod() == _METHOD_List)
		response = new Response( request->getMethod(), mList->toTable() );

	// -------------------------------------------------------------------------------------------------------
	else {
		Log(LOG_WARNING, ":busMessageForMediaList_() FAILED : Method '%s' not found", request->getMethod().c_str() );
	 }

	if ( response == NULL )
		return;

	Message* reply = message.replyTo( response );
	reply->setSource( mList );
	BusCore.postMessage(reply);
 }


void MediaDirectorBundle::busMessageForZone_( MediaZone* zone, const Message& message, const Bus* )
 {
	if ( zone == NULL ) return;

	// check permission
	const Permission* jobPerm = &((const NetworkChannel*)zone)->getPermission();

	bool allowRead = SecurityManager::evalAuthorization( message.getSecurityToken(), jobPerm, Permission::Read );
	if ( !allowRead ) {
		Log(LOG_WARNING, ":busMessageForZone_() : Not allowed to read Host '%s' ", zone->getName().c_str() );
		return;
	 }

	const Request* request = message.getContent()->asRequest();
	Response* response = NULL;

	if ( request->getMethod() == _METHOD_GetProperty )
		response = MPC_mmDoPropertyGet( &message, zone->getProperties(), Log );

	else if ( request->getMethod() == _METHOD_SetProperty )
		response = MPC_mmDoPropertySet( &message, zone->getProperties(), Log );

	else if ( request->getMethod() == _METHOD_ListProperties )
		response = MPC_mmDoPropertyList( &message, zone->getProperties(), Log );

	else if ( request->getMethod() == _MediaZone_METHOD_get_mappings )
		response = new Response( request->getMethod(), zone->getKeyMappingsAsTable() );

	else if ( request->getMethod() == _MediaZone_METHOD_get_medialist )
		response = new Response( request->getMethod(), zone->getPlayList().clone() );

	// -------------------------------------------------------------------------------------------------------
	else {
		Log(LOG_WARNING, ":busMessageForZone_() FAILED : Method '%s' not found", request->getMethod().c_str() );
	 }

	if ( response == NULL )
		return;

	Message* reply = message.replyTo( response );
	reply->setSource( zone );
	BusCore.postMessage(reply);
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// LOAD/SAVE OBJECTs

WRESULT MediaDirectorBundle::loadMediaZones()
 {
	// may also be a folder of .xml files
	string xmlfile = Properties.getValueOf(_MediaDirector_KEY_MediaZoneDB).getData();
	Log(LOG_VERBOSE, ":loadMediaZones() File %s ..", xmlfile.c_str() );
	std::vector<MediaZone*> objects_load;
	WRESULT ret = PersistenceManager::loadObjects( objects_load, "XML", xmlfile );
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadMediaZones", "FAILED#%d loading Database %s..", ret, xmlfile.c_str() );
		return ret;
	 }
	if ( objects_load.size() == 0 ) {
		Log(LOG_WARNING, ":loadMediaZones() : No Items found in database.." );
		return WRET_OK;
	 }

	// register objects
	this->mediaWorker->getZones().mutex().lockForWrite();
	std::vector<MediaZone*>::iterator it_load;
	for ( it_load=objects_load.begin(); it_load!=objects_load.end(); it_load++ ) {
		MediaZone* currObj = *it_load;
		if ( currObj == NULL ) continue;
		if ( currObj->getName().empty() ) continue;
		currObj->getURI().setParent(this->ZoneVirtualURI);
		currObj->getURI().setName(currObj->getName());
		currObj->setPermission( this->getPermission(), Permission::Overwrite_All, true ); // set missing fields
		currObj->setSeen();
		this->mediaWorker->getZones().set_(currObj->getName(),currObj);
	 }
	std::map<string, MediaZone*>::iterator it;
	for ( it=this->mediaWorker->getZones().stdBegin(); it!=this->mediaWorker->getZones().stdEnd(); it++ ) {
		if ( it->second == NULL ) continue;
		if ( !it->second->getParent().empty() ) {
			MediaZone* zone = this->mediaWorker->getZones().key_(it->second->getParent());
			if ( zone != NULL )
				zone->getChildren()[it->first] = "";
		 }
	 }

	this->mediaWorker->getZones().mutex().unLock();
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT MediaDirectorBundle::saveMediaZones()
 {
	FilePath xmlFile;
	if ( !xmlFile.set( Properties.getValueOf(_MediaDirector_KEY_MediaZoneDB_shadow).toString() ) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveMediaZones", "FAILED#1 Invalid Archive File [%s]", xmlFile.getPath().c_str() );
		return WRET_ERR_PARAM;
	 }
	Log(LOG_VERBOSE, ":saveMediaZones() File %s ..", xmlFile.getPathLocal().c_str() );
	this->mediaWorker->getZones().transactionBeginRead();
	WRESULT ret = PersistenceManager::saveObjects( this->mediaWorker->getZones().getMap(), "XML", xmlFile.getPathLocal() );
	this->mediaWorker->getZones().transactionEnd();
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveMediaZones", "FAILED#%d saving Database %s..", ret, xmlFile.getPathLocal().c_str() );
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT MediaDirectorBundle::loadMediaLists()
 {
	// may also be a folder of .xml files
	string xmlfile = Properties.getValueOf(_MediaDirector_KEY_MediaListDB).getData();
	Log(LOG_VERBOSE, ":loadMediaLists() File %s ..", xmlfile.c_str() );
	std::vector<MediaList*> objects_load;
	WRESULT ret = PersistenceManager::loadObjects( objects_load, "XML", xmlfile );
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadMediaLists", "FAILED#%d loading Database %s..", ret, xmlfile.c_str() );
		return ret;
	 }
	if ( objects_load.size() == 0 ) {
		Log(LOG_WARNING, ":loadMediaLists() : No Items found in database.." );
		return WRET_OK;
	 }

	// register objects
	this->mediaWorker->getPlaylists().mutex().lockForWrite();
	std::vector<MediaList*>::iterator it_load;
	for ( it_load=objects_load.begin(); it_load!=objects_load.end(); it_load++ ) {
		MediaList* currObj = *it_load;
		if ( currObj == NULL ) continue;
		if ( currObj->getName().empty() ) continue;
		currObj->getURI().setParent(this->PlaylistVirtualURI);
		currObj->getURI().setName(currObj->getName());
		currObj->setPermission( this->getPermission(), Permission::Overwrite_All, true ); // set missing fields
		this->mediaWorker->getPlaylists().set_(currObj->getName(),currObj);
	 }
	std::map<string, MediaList*>::iterator it;
	for ( it=this->mediaWorker->getPlaylists().stdBegin(); it!=this->mediaWorker->getPlaylists().stdEnd(); it++ ) {
		if ( it->second == NULL ) continue;

	 }

//	Properties.set( _MediaDirector_KEY_count, this->mediaWorker->getZones().size_() );

	this->mediaWorker->getPlaylists().mutex().unLock();
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT MediaDirectorBundle::saveMediaLists()
 {
	FilePath xmlFile;
	if ( !xmlFile.set( Properties.getValueOf(_MediaDirector_KEY_MediaListDB_shadow).toString() ) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveMediaLists", "FAILED#1 Invalid Archive File [%s]", xmlFile.getPath().c_str() );
		return WRET_ERR_PARAM;
	 }
	Log(LOG_VERBOSE, ":saveMediaLists() File %s ..", xmlFile.getPathLocal().c_str() );
	this->mediaWorker->getPlaylists().transactionBeginRead();
	WRESULT ret = PersistenceManager::saveObjects( this->mediaWorker->getPlaylists().getMap(), "XML", xmlFile.getPathLocal() );
	this->mediaWorker->getPlaylists().transactionEnd();
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveMediaLists", "FAILED#%d saving Database %s..", ret, xmlFile.getPathLocal().c_str() );
	 }
	return ret;
 }

/////////////////////////////////////////////////////////////////////////////////////////// LOAD/SAVE OBJECTs
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// LOAD/SAVE OBJECTs

WRESULT MediaDirectorBundle::load_MediaTrack( MediaZone* zone, vector<MediaTrack*>& tracks, MediaDirectorImpl* )
 {
	(void)zone;
	string sqldbtable = Properties.getValueOf(_MediaDirector_KEY_MediaTrackDB).getData();

	WRESULT ret = PersistenceManager::loadObjects( tracks, "SQL", sqldbtable );

	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadObjects", "FAILED#%d loading Database %s..", ret, sqldbtable.c_str() );
		return ret;
	 }
	if ( tracks.size() == 0 ) {
		Log(LOG_WARNING, ":loadObjects() : No Items found in database.." );
		return WRET_OK;
	 }

	return WRET_OK;
 }

WRESULT MediaDirectorBundle::save_MediaTrack( MediaTrack* track, MediaDirectorImpl* )
 {
	return WRET_OK;
 }

/////////////////////////////////////////////////////////////////////////////////////////// LOAD/SAVE OBJECTs
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT MediaDirectorBundle::updateZones() {
	this->zoneParser->updateAll("*");
	return WRET_OK;
 }

void MediaDirectorBundle::updateLocations( const vector<string>& locations )
 {
	Properties.transactionBeginWrite();
	Properties.update(_CommunicatorService_KEY_Locations, Utilities::joinVector(locations, ";") );
	Properties.transactionEnd();

	Response* response = new Response( _METHOD_ListProperties, Properties.toTable(false) );
	Message* message = new Message(response);
	message->setSource(this);
	message->getDestination().fromString("*:Communication");
	message->setSecurityToken( generateCredentials() );
	BusCore.postMessage(message);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT


////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool MediaDirectorBundle::isUserReachable( const string& wosh_username )
 {
	(void)wosh_username; 
	return false;
 }

WRESULT MediaDirectorBundle::sendMessageToUser( const string& wosh_username, const string& message )
 {
	(void)wosh_username; (void)message;
	return WRET_ERR_INTERNAL;
 }

WRESULT MediaDirectorBundle::sendMessageTo( const string& custom_username, const string& message )
 {
	(void)custom_username; (void)message;
	return WRET_ERR_INTERNAL;
 }

WRESULT MediaDirectorBundle::notify( const Notification* notification )
 {
	if ( notification == NULL ) return WRET_ERR_PARAM;
	if ( notification->getNotificationType() != Notification::TYPE_AUDIO ) {
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( !notification->isKindOf<AudioMessage>() ) {
		return WRET_ERR_ILLEGAL_USE;
	 }
	const AudioMessage* audioNotif = dynamic_cast<const AudioMessage*>(notification);

	string location = UserManager::getUserLocation( notification->getRecipent_User());

	Data defPlayer = Properties.getValueOf("Player");
	URI targetPlayer;

	if ( location == "" ) {
		if ( defPlayer.isEmpty() ) {
			Log(LOG_WARNING, ":notify(%d) : User's Location and default Zone are Empty!", notification->getID() );
			return WRET_ERR_INTERNAL;
		 }
		targetPlayer.fromString(defPlayer.getData());
	 }
	else {
		this->mediaWorker->getZones().mutex().lockForRead();

		MediaZone* player = this->mediaWorker->getPlayerOfLocation_(location);
		if ( player == NULL ) {
			this->mediaWorker->getZones().mutex().unLock();
			return WRET_ERR_INTERNAL;
		 }
		targetPlayer = player->getURI();
		this->mediaWorker->getZones().mutex().unLock();
	}

	if ( !targetPlayer.isValid() ) {
		return WRET_ERR_INTERNAL;
	 }

	string filepath = audioNotif->getFile();
	//targetPlayer.fromString("wosh28:Bundles/PlayerStation");

	Request* request = new Request(_Player_METHOD_play, filepath );
	Message* message = new Message(request);
	message->setDestination(targetPlayer);
	message->setDestinationBus(_Bus_Building);
	//signMessage(message);
	message->setSecurityToken( SecurityManager::generateToken(notification->getSender_User()) );

	BusCore.postMessage(message);
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
/*
Response* MediaDirectorBundle::mmDoInspect( const Request* request )
 {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().getData();
	Log(LOG_VERBOSE, ":mmDoListPlayers(%s)", option.c_str() );

	this->mediaWorker->getZones().mutex().lockForRead();
	Table* tbl = new Table( 2 + 1, 2 );
	tbl->setTableName("Players");
	tbl->setHeader(true);
	tbl->set( new Data("Name"), 0, 0);
	tbl->set( new Data("Value"), 0, 1);
	unsigned int iRow = 1;
	tbl->set( new Data("Name"), iRow, 0);
	tbl->set( new Data("Name"), iRow, 1);
	tbl->set( new Data("Mappings"), iRow, 0);
	tbl->set( new Data( it->second-> ), iRow, 1);
	
	this->mediaWorker->getZones().mutex().unLock();
	return new Response( request->getMethod(), tbl );
 }
*/

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* MediaDirectorBundle::mmDoListPlayList( const Request* request )
 {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().getData();
	Log(LOG_VERBOSE, ":mmDoListPlayList(%s)", option.c_str() );

	if ( option == "s" || option == "short" ) {
		List* objs =  new List();
		this->mediaWorker->getPlaylists().mutex().lockForRead();
		std::map<string, MediaList*>::const_iterator it;
		for ( it=this->mediaWorker->getPlaylists().stdBegin(); it!=this->mediaWorker->getPlaylists().stdEnd(); it++ ) {
			objs->add( new Data(it->first) );
		 }
		this->mediaWorker->getPlaylists().mutex().unLock();
		return request->replyResponse(WRET_OK, objs );
	 }
	else {
		this->mediaWorker->getPlaylists().mutex().lockForRead();
		Table* tbl = new Table( this->mediaWorker->getPlaylists().size_(), 4, true );
		tbl->setTableName("Media Lists");
		tbl->setHeaderHorizontal( new Data("Name"), 0);
		tbl->setHeaderHorizontal( new Data("CurrentIndex"), 1);
		tbl->setHeaderHorizontal( new Data("CurrentTrack"), 2);
		tbl->setHeaderHorizontal( new Data("Count"), 3);
		unsigned int iRow = 0;
		std::map<string, MediaList*>::const_iterator it;
		for ( it=this->mediaWorker->getPlaylists().stdBegin(); it!=this->mediaWorker->getPlaylists().stdEnd(); it++ ) {
			if ( it->second == NULL ) continue;
			tbl->set( new Data(it->first), iRow, 0);
			tbl->set( new Data(it->second->getCurrentIndex()), iRow, 1);
			if ( it->second->getCurrentTrack() != NULL )
				tbl->set( new Data(it->second->getCurrentTrack()->getTrack().getPath()), iRow, 2);
			tbl->set( new Data(it->second->getTracks().size()), iRow, 3);
			++iRow;
		 }
		this->mediaWorker->getPlaylists().mutex().unLock();
		return request->replyResponse(WRET_OK, tbl );
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* MediaDirectorBundle::mmDoGetPlayList( const Request* request )
 {
	if ( request == NULL ) return NULL;
	string name = request->getArgument().getData();
	Log(LOG_VERBOSE, ":mmDoGetPlayList(%s)", name.c_str() );

	this->mediaWorker->getPlaylists().mutex().lockForRead();
	MediaList* mList = this->mediaWorker->getPlaylists().key_(name);
	if ( mList == NULL ) {
		this->mediaWorker->getPlaylists().mutex().unLock();
		return request->replyResponse(WRET_ERR_PARAM );
	 }
	MediaList* mList_copy = mList->clone();
	this->mediaWorker->getPlaylists().mutex().unLock();

	return request->replyResponse(WRET_OK, mList_copy );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* MediaDirectorBundle::mmDoGetMatrix( const Request* request )
 {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().getData();
	Log(LOG_VERBOSE, ":mmDoGetMatrix(%s)", option.c_str() );

	this->mediaWorker->getPlaylists().mutex().lockForRead();
	this->mediaWorker->getZones().mutex().lockForRead();

	Table* tbl = new Table( this->mediaWorker->getPlaylists().size_(), this->mediaWorker->getZones().size_(), true, true );
	tbl->setTableName("Media Matrix (Zone/List)");
	unsigned int iCol = 0;
	std::map<string, MediaZone*>::const_iterator it_z;
	for ( it_z=this->mediaWorker->getZones().stdBegin(); it_z!=this->mediaWorker->getZones().stdEnd(); it_z++, iCol++ ) {
		if ( it_z->second == NULL ) continue;
		tbl->setHeaderHorizontal( new Data(it_z->first), iCol);
	 }
	unsigned int iRow = 0;
	std::map<string, MediaList*>::const_iterator it_p;
	for ( it_p=this->mediaWorker->getPlaylists().stdBegin(); it_p!=this->mediaWorker->getPlaylists().stdEnd(); it_p++, iRow++ ) {
		if ( it_p->second == NULL ) continue;
		tbl->setHeaderVertical( new Data(it_p->first), iRow );
		iCol = 0;
		for ( it_z=this->mediaWorker->getZones().stdBegin(); it_z!=this->mediaWorker->getZones().stdEnd(); it_z++, iCol++ ) {
			if ( it_z->second == NULL ) continue;
			if ( it_z->second->getPlayList().getName() != it_p->first ) continue;
			tbl->set( new Data(it_z->second->getZoneRoleAsString()), iRow, iCol);
		 }
	 }

	this->mediaWorker->getZones().mutex().unLock();
	this->mediaWorker->getPlaylists().mutex().unLock();

	return request->Response(WRET_OK, tbl );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* MediaDirectorBundle::mmDoListJoin( const Message* requestMessage )
 {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	Data dTarget;
	Data dPlaylist;
	request->extractArguments(dTarget, dPlaylist);
	if ( dTarget.isEmpty() || dPlaylist.isEmpty() ) {
		if ( request->doOmitResponse() )
			return NULL;
		return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [username, location]" );
	 }
	Log(LOG_VERBOSE, ":mmDoListJoin(%s,%s)", dPlaylist.getData().c_str(), dTarget.getData().c_str() );

	WRESULT ret = this->mediaWorker->copyMediaListToZone( dPlaylist.getData(), dTarget.getData(), "" );
	return request->replyResponse(ret);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* MediaDirectorBundle::mmDoPlay( const Message* requestMessage )
 {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	Data dTarget;
	Data dPlaylist;
	request->extractArguments(dTarget, dPlaylist);
	if ( dTarget.isEmpty() || dPlaylist.isEmpty() ) {
		if ( request->doOmitResponse() )
			return NULL;
		return new Response( request->getMethod(), WRET_ERR_PARAM, "Invalid arguments [username, location]" );
	 }
	Log(LOG_VERBOSE, ":mmDoPlay(%s,%s)", dPlaylist.getData().c_str(), dTarget.getData().c_str() );

	WRESULT ret = this->mediaWorker->synchZoneAgainstPlayer( dPlaylist.getData(), dTarget.getData(), "" );
	return new Response( request->getMethod(), ret );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* MediaDirectorBundle::mmDoSendMessageToUser( const Message* requestMessage )
 {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( !request->hasData() ) {
		if ( request->doOmitResponse() )
			return NULL;
		return new Response( request->getMethod(), WRET_ERR_PARAM, "Invalid data" );
	 }

	std::string username = "";
	std::string message = "";
	if ( request->getData()->isKindOf<List>() ) {
		const List* dl = dynamic_cast<const List*>(request->getData());
		if ( dl->size() < 2 ) {
			if ( request->doOmitResponse() )
				return NULL;
			return new Response( request->getMethod(), WRET_ERR_PARAM, "Invalid arguments [username, message]" );
		 }
	//	username = dl-(0).getData();
	//	dl->join(message, " ", 1);
	 }

	std::string sender = "";
	if ( requestMessage->getSecurityToken() != NULL )
		sender = requestMessage->getSecurityToken()->getUser();

	Log(LOG_INFO, "mmDoSendMessageToUser(%s) : %d chars", username.c_str(), message.size() );

	if ( sender != username && sender != "wosh ")
		message += " [by " + sender + "]";

	WRESULT ret = sendMessageToUser( username, message );

	if ( request->doOmitResponse() )
		return NULL;
	if ( WSUCCEEDED(ret) )
		return new Response( request->getMethod(), ret, "Sent!" );
	return new Response( request->getMethod(), ret, "Internal Error!" );
 }


///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
